/*
 * @(#)AVTransmit2.java	1.4 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

package javamail.rtp;

import java.io.*;
import javax.media.*;
import javax.media.control.BitRateControl;
import javax.media.control.FramePositioningControl;
import javax.media.protocol.*;
import javax.media.protocol.DataSource;
import javax.media.format.*;
import javax.media.control.TrackControl;
import javax.media.rtp.*;

public class rtpTransmit {

    // Input MediaLocator
    // Can be a file or http or capture source
    private MediaLocator locator;
    private String ipAddress;
    private int portBase;
    private AudioFormat[] reqFormats = null;

    private Processor processor = null;
    private RTPManager rtpMgr;
    private DataSource dataOutput = null;
    
    public rtpTransmit(RTPManager mgr, AudioFormat[] formats) {
	this.rtpMgr = mgr;
        this.reqFormats = formats;
    }

    /**
     * Starts the transmission. Returns null if transmission started ok.
     * Otherwise it returns a string with the reason why the setup failed.
     */
    public synchronized String start() {
	String result;

        //System.err.println("rtpTransmit:start()-In function");
	// Create a processor for the specified media locator
	// and program it to output JPEG/RTP
	result = createProcessor();
	if (result != null)
	    return result;

        //System.err.println("rtpTransmit:start()-After creating processor");
	// Create an RTP session to transmit the output of the
	// processor to the specified IP address and port no.
	result = createTransmitter();
	if (result != null) {
	    processor.close();
	    processor = null;
	    return result;
	}

        //System.err.println("rtpTransmit:start()-After creating transmitter");
	// Start the transmission
	//processor.start();

        //System.err.println("rtpTransmit:start()-Successfully started!");
	return null;
    }

    /**
     * Stops the transmission if already started
     */
    public void stop() {
	synchronized (this) {
	    if (processor != null) {
		processor.stop();
		processor.close();
		processor = null;
	    }
	}
    }

    private String createProcessor() {
	if (locator == null)
	    return "Locator is null";

	DataSource ds;

	try {
	    ds = javax.media.Manager.createDataSource(locator);
	} catch (Exception e) {
	    return "Couldn't create DataSource";
	}
    /**
     * Starts the transmission. Returns null if transmission started ok.
     * Otherwise it returns a string with the reason why the setup failed.
     */

	// Try to create a processor to handle the input media locator
	try {
	    processor = javax.media.Manager.createProcessor(ds);
	} catch (NoProcessorException npe) {
	    return "Couldn't create processor";
	} catch (IOException ioe) {
	    return "IOException creating processor";
	} 

	// Wait for it to configure
	boolean result = waitForState(processor, Processor.Configured);
	if (result == false)
	    return "Couldn't configure processor";

	// Get the tracks from the processor
	TrackControl [] tracks = processor.getTrackControls();

	// Do we have atleast one track?
	if (tracks == null || tracks.length < 1)
	    return "Couldn't find tracks in processor";

	// Set the output content descriptor to RAW_RTP
	// This will limit the supported formats reported from
	// Track.getSupportedFormats to only valid RTP formats.
	ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
	processor.setContentDescriptor(cd);

	Format supported[];
	Format chosen = null;
	boolean atLeastOneTrack = false;

	// Program the tracks.
        // SRIRAM has changed the following
	for (int i = 0; i < tracks.length; i++) {
	    if (tracks[i].isEnabled()) {
		supported = tracks[i].getSupportedFormats();
                for(int indexFormat=0; indexFormat<reqFormats.length; indexFormat++){
                    for(int indexSupp=0; indexSupp<supported.length; indexSupp++) {
                        if(!(supported[indexSupp] instanceof AudioFormat))
                            break;
                        if(supported[indexSupp].isSameEncoding(reqFormats[indexFormat])) {
                            chosen = reqFormats[indexFormat];
                            tracks[i].setFormat(chosen);
                            System.err.println("Track " + i + " is set to transmit as:");
                            System.err.println("  " + chosen);
                            atLeastOneTrack = true;
                        }
                    }
                    if(chosen!=null)
                        break;
                }
                if(chosen == null)
                    tracks[i].setEnabled(false);
	    } else
		tracks[i].setEnabled(false);
	}

	if (!atLeastOneTrack)
	    return "Couldn't set any of the tracks to a valid RTP format";
        else {
            System.out.println("rtpTransmit::createProcessor-Tracks: "+chosen.toString());
        }

	// Realize the processor. This will internally create a flow
	// graph and attempt to create an output datasource for JPEG/RTP
	// audio frames.
        result = waitForState(processor, Controller.Realized);
	if (result == false)
	    return "Couldn't realize processor";

	// Get the output data source of the processor
	dataOutput = processor.getDataOutput();

	return null;
    }

    public void setLocator(MediaLocator ml) {
        this.locator = ml;
    }

    public String startPlaying() {
        try {
            //processor.syncStart(processor.getMediaTime());
            processor.start();
        } catch (Exception e) {
            return "Error occured while restarting processor: "+e.getMessage();
        }
        return null;
    }

    public String stopPlaying() {
        try {
            processor.stop();
        } catch (Exception e) {
            return "Error occured during stopping play: "+e.getMessage();
        }
        return null;
    }

    public String setProcessorTime(int jump) {
        try {
            processor.setMediaTime(new Time(jump));
            //FramePositioningControl fpc = (FramePositioningControl)processor.getControl("javax.media.control.FramePositioningControl");
            //if(fpc!=null)
            //    fpc.seek(jump);
        } catch (Exception e) {
            return "Error occured while setting time: "+e.getMessage();
        }
        return null;
    }

    public String jumpProcessorTime(int jump) {
        try {
            processor.setMediaTime(new Time(processor.getMediaTime().getSeconds() + jump));
            //FramePositioningControl fpc = (FramePositioningControl)processor.getControl("javax.media.control.FramePositioningControl");
            //if(fpc!=null) {
            //    fpc.skip(jump);
            //}
        } catch (Exception e) {
            return "Error occured while skipping: "+e.getMessage();
        }
        return null;
    }

    public String setProcessorRate(int rate) {
        try {
            //processor.stop();
            processor.setRate(rate);
            Control c = processor.getControl("javax.media.control.BitRateControl");
            if (c!=null) {
                BitRateControl brc = (BitRateControl) c;
                brc.setBitRate((int)brc.getBitRate()*rate);
            }
            //processor.start();
            System.err.println("rtpTransmit:setProcessRate()- Current rate:"+processor.getRate());
        } catch (Exception e) {
            return "Error occured while setting rate: "+e.getMessage();
        }
        return null;
    }


    /**
     * Use the RTPManager API to create sessions for each media 
     * track of the processor.
     */
    private String createTransmitter() {
        try {
            if(rtpMgr==null)
                return "RTP manager is still null!!";
            
            SendStream sendStream = rtpMgr.createSendStream(dataOutput, 0);
            sendStream.start();
        } catch (Exception  e) {
            return e.getMessage();
        }

	return null;
    }

    /****************************************************************
     * Convenience methods to handle processor's state changes.
     ****************************************************************/
    Object waitEndOfMedia = new Object();

    /**
     * Block until the processor has transitioned to the given state.
     * Return false if the transition failed.
     */
    void waitForEndOfMedia() {
	synchronized (waitEndOfMedia) {
	    try {
                waitEndOfMedia.wait();
	    } catch (Exception e) {}
	}
	return;
    }

    
    private Integer stateLock = new Integer(0);
    private boolean failed = false;
    
    Integer getStateLock() {
	return stateLock;
    }

    void setFailed() {
	failed = true;
    }
    
    private synchronized boolean waitForState(Processor p, int state) {
	p.addControllerListener(new StateListener());
	failed = false;

	// Call the required method on the processor
	if (state == Processor.Configured) {
	    p.configure();
	} else if (state == Processor.Realized) {
	    p.realize();
	}
	
	// Wait until we get an event that confirms the
	// success of the method, or a failure event.
	// See StateListener inner class
	while (p.getState() < state && !failed) {
	    synchronized (getStateLock()) {
		try {
		    getStateLock().wait();
		} catch (InterruptedException ie) {
		    return false;
		}
	    }
	}

	if (failed)
	    return false;
	else
	    return true;
    }

    /****************************************************************
     * Inner Classes
     ****************************************************************/

    class StateListener implements ControllerListener {

	public void controllerUpdate(ControllerEvent ce) {
            //System.err.println("rtpTransmit:controllerUpdate()-Received event: "+ce);

	    // If there was an error during configure or
	    // realize, the processor will be closed
	    if (ce instanceof ControllerClosedEvent)
		setFailed();

            // Look for end of media event
            if (ce instanceof EndOfMediaEvent) {
                System.err.println("Transmitting has stopped!");
                synchronized(waitEndOfMedia) {
                    waitEndOfMedia.notifyAll();
                }
                stop();
            }

            // Look for rate change event
            if(ce instanceof RateChangeEvent) {
                System.err.println("rtpTransmit:controllerUpdate()- Processor rate changed to: "+processor.getRate());
            }

	    // All controller events, send a notification
	    // to the waiting thread in waitForState method.
	    if (ce instanceof ControllerEvent) {
		synchronized (getStateLock()) {
		    getStateLock().notifyAll();
		}
	    }
	}
    }
}
